Skip to content

feat(stt): conversation-aware recognition — keyterms + chat context#1967

Merged
toubatbrian merged 4 commits into
mainfrom
brian/stt-keyterms
Jul 7, 2026
Merged

feat(stt): conversation-aware recognition — keyterms + chat context#1967
toubatbrian merged 4 commits into
mainfrom
brian/stt-keyterms

Conversation

@toubatbrian

@toubatbrian toubatbrian commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Port of the Python PR livekit/agents#6039 — conversation-aware STT recognition.

  • STT base (stt/stt.ts): new keyterms / chatContext capabilities and internal _updateSessionKeyterms / _pushConversationItem hooks (warn-once when unsupported). FallbackAdapter merges capabilities and fans out both hooks; StreamAdapter delegates to the wrapped STT.
  • KeytermDetector (voice/keyterm_detection.ts): maintains static + auto-detected keyterms. On each user turn (gated by turnInterval, single-flight, 10s timeout) it snapshots the transcript and runs a forced record_keyterms tool call against a detection LLM (default google/gemma-4-31b-it via the inference gateway). Terms move pending → confirmed with TTL eviction and an optional maxKeyterms cap; LLM metrics are re-emitted through the session.
  • Session/activity wiring: AgentSession accepts keytermsOptions, exposes session.keyterms, and supports updating static terms via updateOptions; AgentActivity starts/stops the detector with the activity's STT and subscribes conversation_item_added_pushConversationItem for chat-context-capable STTs.
  • Plugins: Deepgram v1 (keyterm), Deepgram v2/Flux (keyterms), and inference STT (provider-mapped extra) apply merged user + session keyterms, deferring mid-speech updates until end of speech. AssemblyAI adds keyterms plus agent_context carryover (agentContextCarryover, U3-Pro models) implementing the chatContext capability.
  • Gateway fix (inference/llm.ts): strip $schema from tool JSON schemas. The zod v3 conversion embeds a draft 2019-09 $schema URI that makes some gateway deployments silently end the stream with no tool call; pydantic never emits the key, so this also matches what Python sends. (Non-mutating: destructured, so caller-supplied raw schemas are untouched.)

Python's Google STT hunk is intentionally not ported (no Google STT plugin exists in JS).

Test plan

  • 33 unit tests ported 1:1 from tests/test_stt_context.py (keyterm_detection.test.ts) — state machine, STT binding, chat context sink, triggering, helpers.
  • Live e2e per plugin (scripted 3-turn conversations with distinctive names): Deepgram v1, Deepgram v2, AssemblyAI, and inference STT all show pending → confirm promotion and the merged keyterm set reaching the recognizer; AssemblyAI additionally forwards assistant turns as agent_context.
  • Hunk-by-hunk parity audit against the Python diff: full parity across all file pairs.
  • Build, lint, and Prettier green (api:check failure is pre-existing on main).

Port of livekit/agents#6039 (Python). Adds framework-managed STT keyterms
with optional LLM-based auto-detection (KeytermDetector), plus a chat
context sink so STTs with native carryover can bias recognition on the
live conversation.

- stt: `keyterms`/`chatContext` capabilities and `_updateSessionKeyterms` /
  `_pushConversationItem` hooks (forwarded by Fallback/Stream adapters)
- voice: KeytermDetector with pending->confirm state machine, TTL eviction,
  turn-interval gating and forced record_keyterms tool call
- plugins: deepgram v1/v2 + inference STT keyterm support (deferred while
  speaking), assemblyai keyterms + agent_context carryover
- inference: strip `$schema` from tool JSON schemas — the zod v3 draft
  2019-09 URI makes some gateway deployments end the stream with no tool
  call (pydantic never emits it, so this also matches Python)

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bd34369

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 35 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@toubatbrian toubatbrian requested a review from longcw July 6, 2026 08:47
devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: Cursor <cursoragent@cursor.com>
chatgpt-codex-connector[bot]

This comment was marked as resolved.

@longcw longcw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Comment thread agents/src/voice/keyterm_detection.ts
…terms on bind

- thread the detect task's abort signal into runOnce/detectKeyterms so
  aclose() unblocks an in-flight LLM pass immediately (parity with
  Python's asyncio cancellation) instead of waiting out the detection
  timeout; a cancelled pass no longer touches keyterm state
- push the current (possibly empty) keyterm set to a keyterm-capable STT
  on first bind, so a reused STT instance can't retain session keyterms
  from a previous detector

Co-authored-by: Cursor <cursoragent@cursor.com>
@toubatbrian toubatbrian requested a review from a team as a code owner July 7, 2026 09:04

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread agents/src/inference/stt.ts
Comment thread plugins/deepgram/src/stt.ts
updateOptions was baking the active session keyterms into
this.opts.modelOptions, so a later _updateSessionKeyterms pass read the
polluted baseline and could never remove previously applied terms.
Session keyterms are now only merged into what is sent to live streams
and computed on the fly at connect time, mirroring the deepgram and
assemblyai plugins' separate user/session tracking.

Co-authored-by: Cursor <cursoragent@cursor.com>
@toubatbrian toubatbrian merged commit 8ded843 into main Jul 7, 2026
9 checks passed
@toubatbrian toubatbrian deleted the brian/stt-keyterms branch July 7, 2026 13:50
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants